middleware パターンに対する考察
middlewareとは、ここではexpress等の、requestを受けてresponseを返すような構造の構成要素として使われるものを指す https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index/_static/request-delegate-pipeline.png?view=aspnetcore-8.0
これは、全体としては入力を受けて、出力を返すだけの関数である。
これを、層状にスライスして、どんどん層をフォールバックさせるような構造にしたものがmiddlewareである
code:js
function donothingMiddleware(req,res,next) {
return next(req)
}
code:ts
type Reducer = (state: State, action: Action) => State
入力が前ステートとアクション
出力が新しいステート
middlewareはこのように書ける。
code:ts
function middlewareExample(state:State, action:Action, next:(state:State,action:Action)=>State) {
console.log('before')
const nextState = next(state,action)
console.log('after')
return nextState
}
さて、ここで、入出力の型をちょっと工夫してみよう
まず、nextはReducer型であることに注意したい。
その上で、nextのみを先に引数として受け取る形でカリー化する
code:ts
const middlewareExample = (next: Reducer) => (state:State, action:Action) => {
console.log('before')
const nextState = next(state,action)
console.log('after')
return nextState
}
そうすると、nextを受け取り引数をひとつ受け取った後に返す型が、これもまたReducerであることが分かる
code:ts
const middlewareExample: (next:Reducer) => Reducer = (next) => (state, action) => {
console.log('before')
const nextState = next(state,action)
console.log('after')
return nextState
}
これを改めて、Middleware型としよう
code:ts
type Middleware = (next:Reducer) => Reducer
const middlewareExample: Middleware = (next) => (state, action) => {
console.log('before')
const nextState = next(state,action)
console.log('after')
return nextState
}
さて、この形のmiddlewareを複数結合するにはどうすればいいか?
console.logしたり、型をみながらイジるとわかるが、こんな感じだ
code:ts
function combine(...middlewares: Middleware[]): Middleware {
return (initialReducer) =>
middlewares.reduceRight(
(next, middleware) => middleware(next),
initialReducer
);
}
複数のmiddlewareを繋げたら、ひとつのmiddlewareになることを言っている
code:ts
const passthrough =
(s: string): Middleware =>
(next) =>
(state, action) => {
console.log("start", s);
const nextState = next(state, action);
console.log("end", s);
return nextState;
};
const m = combine(
passthrough("1"),
passthrough("2"),
passthrough("3"),
passthrough("4")
);
m((state) => {
console.log("端っこです");
return state;
})(initialState, action);
code:log
start 1
start 2
start 3
start 4
端っこです
end 4
end 3
end 2
end 1
こういう感じになる
middlewareの配列自体もreduceしてるから、頭がこんがらがるかも
いざ作ってみたが、次のstateを求めるためのreducerをmiddleware的に、後続処理を扱えるようにするメリットを、現状まだ遭遇できてない
後続処理がなにか選択範囲を求めるようなもので、それに対する操作をしたいときとか?
そういうときも、選択と操作は別のアクションで各々実行されるからなあ
早期returnは必要であるのは分かる
なにか後続の処理が大きいステートを指していて、そのステートが抜けて親に任せるケースとかには使えるかも